#ifndef xpack_graphic_algo_graylize
#define xpack_graphic_algo_graylize
#pragma push_macro("xuser")
#undef  xuser
#define xuser mixc::graphic_algo_graylize::inc
#include"algo/heap_root.hpp"
#include"define/base_type.hpp"
#include"interface/seqptr.hpp"
#include"macro/xalways_inline.hpp"
#include"macro/xexport.hpp"
#pragma pop_macro("xuser")

namespace mixc::graphic_algo_graylize::origin{
    struct graylize_pack{
        uxx                 x_start = 0;
        uxx                 y_start = 0;
        uxx                 width;
        uxx                 height;
        inc::seqptr<u16>    line_buffer_x2; // 需要两行缓存，元素个数为 width * 2，元素类型为 u16 的数组
        uxx                 dark_value = 400;

        /*
        // 这个接口可以实现类似 PIL 1bit 图像的效果，但如果是动态移动的图片会有闪烁的情况
        graylize(..., [](uxx x, uxx y, u08 gray){
            graylize_pack::binarization(x, y, [&](u08 gray){
                set_pixel(x, y, gray);
            })
        });
        */

        static void binarization(uxx x, uxx y, u08 gray, auto const & rander){
            auto i                  = uxx(gray >> 6);
            if (i == 0x1){
                i                  += x & y & 1;
            }
            else if (i == 0x02){
                i                  -= x & y & 1;
            }

            auto gray08             = u08(i >> 1 ? 0xff : 0x00);
            rander(gray08);
        }

        static void binarization_x8(uxx x, uxx y, u08 gray, auto const & rander){
            /*
            // 查找表生成
            // 算法要领
            // 随着 gray 值向 0x00(纯黑) 或者 0xff(纯白) 变化，靠近黑的一侧变白的概率下降，靠近白的一侧变黑的概率下降
            // 这反映在坐标点上，这些点只是概率性的出现在坐标上，所以由纯色点密度来决定明暗程度，
            // 如果白色的点越密集，那么它更偏向于白色，如果黑色点更密集，那么它更偏向于黑色。处于黑白两端的点不再需要向白黑对端跳变
            //
            // PS:
            // 如果图像本身很白，那么可以将 dark_value 调高一些，反之则调低一些。
            #define xuser mixc::user
            #include"io/tty.hpp"
            #include"mixc.hpp"
            #define inc xuser

            void gen(u08 gray, uxx x, uxx y){
                auto i                  = uxx(gray >> 5);
                auto x0                 = x >> 1;
                auto y0                 = y >> 1;

                if (i == 0x1){
                    if (((x & y) & (x0 & (y0 ^ x))) & 1){
                        i               = 4; // 变成白色
                    }
                }
                else if (i == 0x2){
                    if (((x & y) & (((x0 ^ x) | (y ^ y0)))) & 1){
                        i               = 4; // 变成白色
                    }
                }
                else if (i == 0x3){
                    i                  += ((x & y) & 1);
                }
                else if (i == 0x4){
                    i                  -= ((x & y) & 1);
                }
                else if (i == 0x5){
                    if (((x & y) & (((x0 ^ x) | (y ^ y0)))) & 1){
                        i               = 0; // 变成黑色
                    }
                }
                else if (i == 0x6){
                    if (((x & y) & (x0 & (y0 ^ x))) & 1){
                        i               = 0; // 变成黑色
                    }
                }

                u08 v                   = uxx(i >> 2) ? 0xff : 0x00;
                inc::tty.write("\v, ", inc::zx{v});
            }

            xinit(inc::the_main){
                inc::tty.write_line("static constexpr u08 lut[] = {");
                for(uxx gray = 0; gray < 8; gray++){
                    inc::tty.write("    ");
                    for(uxx y = 0; y < 4; y++){
                        for(uxx x = 0; x < 4; x++){
                            gen(gray << 5, x, y);
                        }
                    }
                    inc::tty.write_line();
                }
                inc::tty.write_line("};");
            };
            
            */
            static constexpr u08 lut[] = {
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
                0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 
                0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 
                0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 
                0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
            };

            auto gray08                 = lut[((gray >> 1) & 0x70) | ((x << 2) & 0xc) | (y & 0x3)];
            rander(gray08);
        }
    };

    inline void xalways_inline graylize(
        auto                    const & input, 
        graylize_pack           const & pack,
        auto                    const & randerx
    ){
        auto width                      = pack.width;
        auto height                     = pack.height;
        auto x_start                    = pack.x_start;
        auto y_start                    = pack.y_start;
        auto x_end                      = uxx(x_start + width);
        auto y_end                      = uxx(y_start + height);

        // line_buffer 需要两行
        if (pack.line_buffer_x2->length < width * 2 or height <= 2 or width <= 2){
            return;
        }

        auto & line_buffer              = pack.line_buffer_x2;
        auto dark_value                 = pack.dark_value;
        auto get_brightness             = [](auto & v){
            return uxx(v->red * 3 + v->green * 6 + v->blue * 1);
        };

        auto line0                      = line_buffer;
        auto line1                      = line_buffer.backward(width);

        for(uxx x = x_start; x < x_end; x += 1){
            auto v0                     = input[y_start][x];
            auto v1                     = input[y_start + 1][x];
            auto brightness0            = get_brightness(v0);
            auto brightness1            = get_brightness(v1);
            line0[x - x_start]          = brightness0;
            line1[x - x_start]          = brightness1;
        }

        for(uxx y = y_start + 2; y < y_end; y += 1){
            auto line2_left             = get_brightness(input[y][x_start]);
            auto line2_middle           = get_brightness(input[y][x_start + 1]);
            auto new_second             = line2_middle;
            auto line0_left             = line0[0];
            auto line0_middle           = line0[1];
            auto line0_right            = line0[2];
            auto line0_sum012           = line0_left + line0_middle + line0_right;

            auto line1_left             = line1[0];
            auto line1_middle           = line1[1];
            auto line1_right            = line1[2];
            auto line1_sum02            = line1_left + line1_right;

            for(uxx x = x_start + 2; x + 1 < x_end; x += 1){
                auto line2_right        = get_brightness(input[y][x]);
                auto line2_sum012       = line2_left + line2_middle + line2_right;
                auto sum                = line0_sum012 + line1_sum02 + line2_sum012;

                // 比 1/8 小一些即可，因为 sum 表示当前像素周围 8 个像素的亮度值
                // 这里使用除法优化，分数接近于一个小于 1/8 的值，除以 4096 一般会被优化成移位操作
                // 
                // 提高原始像素对比度，放大中间像素和周围像素的差异，原始的 4gray 转换会丢失一些细节信息
                // red:3 + green:6 + blue:1 = 10
                if (uxx avg = sum * dark_value / 4096, m2 = line1_middle * 2; avg > line1_middle){
                    randerx(x - 2, y, m2 < avg ? 0 : (m2 - avg) / 10); // 计算结果在 0~3
                }
                else{
                    auto v              = (m2 - avg) / 10;
                    randerx(x - 2, y, v > 0xff ? 0xff : v);
                }

                line0_left              = line0_middle;
                line0_middle            = line0_right;
                line0_right             = line0[x + 1 - x_start];
                line0_sum012            = line0_left + line0_middle + line0_right;

                line1_left              = line1_middle;
                line1_middle            = line1_right;
                line1_right             = line1[x + 1 - x_start];
                line1_sum02             = line1_left + line1_right;

                line2_left              = line2_middle; // 先
                line2_middle            = line2_right;  // 后
                line0[x - x_start]      = line2_right;
            }

            line0[0]                    = line2_left;
            line0[1]                    = new_second;

            // 指针变换 line0 里面已经是 line2 元素的数据了，现在让 line1 变成 line0, line2 变成 line1
            // 进行下一轮循环
            auto line2                  = line0;
            line0                       = line1;
            line1                       = line2;
        }
    }
}

#endif

xexport_space(mixc::graphic_algo_graylize::origin)
